#include "rrqAck.h"
#include "../logger/logger.h"
#include "../serverDefines.h"
#include <sys/types.h>	/*recvfrom*/
#include <sys/socket.h>	/*recvfrom*/
#include <sys/select.h> /*select*/
#include <arpa/inet.h> /*for htons*/
#include <stdio.h> /*for perror*/
#include <errno.h> /*for perror*/
#include <netinet/in.h> /*for sockadder_in*/
#include <string.h> /*for memset*/
#include <unistd.h> /*calloc*/
#include <stdlib.h> /*exit,calloc*/

//bitmask for recognising high and low bytes
enum mask{highByte=0xffff0000, lowByte=0x0000ffff};

//forward declaration for errorMsg
msg_p errorMsg(msg_p message,int errNum,char* msg);

/**
 * author: Haim Tsolker.
 * 
 * acknowledgePacket
 * 
 * socket - socket file discriptor
 * toAck  - package number to be acknowledge
 * remote - to be acknowledge from
 * 
 * return - on seccess return the number of pack to be ack
 * 			on fail return RTT=5
 */
msg_p acknowledgePacket(request_p request, msg_p data)
{
	msg_t response;
	int numbytes;
	int retval;
	request_p requestStruct = (request_p) request;
	struct sockaddr_in responseAddr;
	struct sockaddr_in *remoteDuplicate;
	char buf[BLOCKSIZE + HEADERSIZE + 1] = {'\0'};
	socklen_t remoteAddrLen;
	struct sockaddr* remoteAddr=(struct sockaddr*)&responseAddr;
	remoteDuplicate = requestStruct->remoteAddr;
	remoteAddrLen = sizeof(struct sockaddr);
	struct timeval timeval_ack;
	fd_set socketfd;
	fd_set sockrecv;
	FD_ZERO (&socketfd);
	FD_SET (requestStruct->serverSocket, &socketfd);
	timeval_ack.tv_sec=getRTT();
	timeval_ack.tv_usec=0;
	while((timeval_ack.tv_sec|timeval_ack.tv_usec)>0)
	{
		//as long as time is != 0
		sockrecv = socketfd;
		if ((retval = select (requestStruct->serverSocket + 1, &sockrecv, NULL, NULL, &timeval_ack)) == -1)
		{
			//problem with select - error if logged and sent to client
			logMessage("acknowledgePacket: select error\n");
			response = *(errorMsg(&response,unKnownTransferID,"Out of resource - try later"));
			if((numbytes = sendto(requestStruct->serverSocket,&response,response.size + HEADERSIZE,0,(struct sockaddr *)requestStruct->remoteAddr, sizeof(struct sockaddr)))==-1)
			{
				//cant send to client
				logMessage("acknowledgePacket: sendto error\n");
			}
			else
			{
				//log - error message was sent
				logMessage("acknowledgePacket: sent %d bytes to %s:%d\n", numbytes, inet_ntoa(requestStruct->remoteAddr->sin_addr),ntohs(request->remoteAddr->sin_port));	
			}
			data->size=0;
			return data;
		}
		if(0==(timeval_ack.tv_sec|timeval_ack.tv_usec))
		{
			//timeout event
			data->attempt++;
			return data;
		}
		if(FD_ISSET(requestStruct->serverSocket,&sockrecv))
		{
			//select has determined that data is waiting to be read
			if ((numbytes=recvfrom(requestStruct->serverSocket, buf, sizeof(buf)-1 , 0,remoteAddr,&remoteAddrLen))==-1)
			{
				//unable to receive the data 
				logMessage("acknowledgePacket: recvfrom error\n");
				continue;
			}
			logMessage("acknowledgePacket: recv %d bytes from %s:%d\n", numbytes, inet_ntoa(responseAddr.sin_addr),ntohs(responseAddr.sin_port));
			
			if((inet_ntoa(responseAddr.sin_addr) != inet_ntoa(requestStruct->remoteAddr->sin_addr)) || (ntohs(responseAddr.sin_port) != ntohs(requestStruct->remoteAddr->sin_port)))
			{
				//send Error message to wrong TID and return to wait right packet
				response = *(errorMsg(&response,unKnownTransferID,"wrong TID"));
				if((numbytes = sendto(requestStruct->serverSocket,&response,response.size + HEADERSIZE,0,remoteAddr, sizeof(struct sockaddr)))==-1)
				{
					logMessage("acknowledgePacket: recvfrom error\n");
					continue;
				}
				logMessage("acknowledgePacket: recv %d bytes from %s:%d - drop missing packet\n", numbytes, inet_ntoa(responseAddr.sin_addr),ntohs(responseAddr.sin_port));
				continue;
			}
			
			memcpy(&response,buf,HEADERSIZE);
			if(response.msgID==(response.msgID&htons(ackRequest)))
			{
				//msgId is the same in the request and response
				if ((response.msgNumber)==(data->msgNumber))
				{
					//success
					data->attempt = 0;
					return data;
				}
				else
				{
					logMessage("acknowledgePacket: Received an acknowledgement for the wrong packet <%d> from %s:%d\n",response.msgNumber, inet_ntoa(responseAddr.sin_addr),ntohs(responseAddr.sin_port));
				}
			}
			else
			{
				logMessage("acknowledgePacket: Bad Ack from %s:%d\n", inet_ntoa(responseAddr.sin_addr),ntohs(responseAddr.sin_port));
			}
		}
	}
	return data;
}

/**
 * author: Haim Tsolker.
 * 
 * errorMsg
 * 
 * message - message to be filled with error data
 * errNum - error number
 * msg - additional error message
 * 
 * return error message
 * 
 * This function creates an error message with error number errNum and 
 * additional message msg
 */
msg_p errorMsg(msg_p message,int errNum,char* msg)
{
	message->msgID = ntohs(errRequest);
	message->msgNumber = ntohs(errNum);
	if(strlen(msg)>BLOCKSIZE? (BLOCKSIZE):0)
	{
		msg[BLOCKSIZE-1]='\0';
	};
	memccpy((void*)(message->data),(void*)msg,'\0',strlen(msg)+1);
	message->size = strlen(message->data)+1;
	return message;
}
